package com.hiof.servercommunication;

import android.graphics.Bitmap;
import com.google.gson.Gson;
import com.hiof.beans.UserInfo;
import com.hiof.servercommunication.exception.NetworkException;
import com.hiof.servercommunication.exception.ServerException;
import com.hiof.servercommunication.push.beans.MediaSendPacket;
import com.hiof.servercommunication.receive.beans.*;
import com.hiof.servercommunication.status.LoginStatus;
import com.hiof.servercommunication.status.StartRouteStatus;
import com.hiof.utility.Utility;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.ByteArrayBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.CoreProtocolPNames;

import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

//import com.hiof.servercommunication.receive.beans.AllRouteInfoPacket;

public class RestFulServiceManager {

    private static HttpClient httpClient;
    //    private static HttpPost httppost;
    private static Gson gson;
    private static RestFulServiceManager instance;
    private static final String SERVER_URL = "http://klimatur.hiof.no:8887/norsemanserver";

    private RestFulServiceManager() {
        init();
    }

    private void init() {
        httpClient = new DefaultHttpClient();
        httpClient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);
        gson = new Gson();
    }

    public static RestFulServiceManager getInstance() {
        if (instance == null) {
            instance = new RestFulServiceManager();
        }
        return instance;
    }

    //TODO call shutdown when the applicaion exits
    public void shutDownService() {
        httpClient.getConnectionManager().shutdown();
        httpClient = null;
        if (instance != null)
            instance = null;
    }

    public AcknowledgePacket sendAthleteLocationInfo(int userId, int athleteId, double lat, double lng, double height, double speed)
            throws NetworkException, ServerException {
        AcknowledgePacket packet = new AcknowledgePacket("ok");
        try {
            List<NameValuePair> data = new ArrayList<NameValuePair>();
            String restfulURL = SERVER_URL + "/restfulservice/liveLocation/athlete";
            HttpPost httppost = new HttpPost();
            httppost.setURI(new URI(restfulURL));
            data.add(new BasicNameValuePair("userId", String.valueOf(userId)));
            data.add(new BasicNameValuePair("athleteId", String.valueOf(athleteId)));
            data.add(new BasicNameValuePair("lat", String.valueOf(lat)));
            data.add(new BasicNameValuePair("lng", String.valueOf(lng)));
            data.add(new BasicNameValuePair("high", String.valueOf(height)));
            data.add(new BasicNameValuePair("speed", String.valueOf(speed)));
            httppost.setEntity(new UrlEncodedFormEntity(data));

            if (httpClient == null)
                init();
            HttpResponse response = httpClient.execute(httppost);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 200 && statusCode != 201) {
                String errorString = response.getStatusLine().getReasonPhrase();
                packet.setMessage("Server Error");
                Utility.error(this.getClass().toString() + " sendAthleteLocationInfo()", errorString);
                throw new ServerException(errorString, statusCode);
            } else {
                Utility.debug(this.getClass().toString() + " sendAthleteLocationInfo()", "Athlete Location Info sent to server");
            }
        } catch (Exception ex) {
            Utility.error(this.getClass().toString() + " sendAthleteLocationInfo()", ex.getMessage());
            throw new NetworkException(ex.getMessage());
        }
        return packet;
    }

    public AcknowledgePacket sendSupportLocationInfo(int userId, int supportId, double lat, double lng, double height, double speed)
            throws NetworkException, ServerException {
        AcknowledgePacket packet = new AcknowledgePacket("ok");
        try {
            List<NameValuePair> data = new ArrayList<NameValuePair>();
            String restfulURL = SERVER_URL + "/restfulservice/liveLocation/support";

            HttpPost httppost = new HttpPost();
            httppost.setURI(new URI(restfulURL));
            data.add(new BasicNameValuePair("userId", String.valueOf(userId)));
            data.add(new BasicNameValuePair("supportId", String.valueOf(supportId)));
            data.add(new BasicNameValuePair("lat", String.valueOf(lat)));
            data.add(new BasicNameValuePair("lng", String.valueOf(lng)));
            data.add(new BasicNameValuePair("high", String.valueOf(height)));
            data.add(new BasicNameValuePair("speed", String.valueOf(speed)));
            httppost.setEntity(new UrlEncodedFormEntity(data));

            if (httpClient == null)
                init();
            HttpResponse response = httpClient.execute(httppost);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 200 && statusCode != 201) {
                String errorString = response.getStatusLine().getReasonPhrase();
                packet.setMessage("Server Error");
                Utility.error(this.getClass().toString() + " sendSupportLocationInfo()", errorString);
                throw new ServerException(errorString, statusCode);
            } else {
                Utility.debug(this.getClass().toString() + " sendSupportLocationInfo()", "Support Location Info sent to server");
            }
        } catch (Exception ex) {
            Utility.error(this.getClass().toString() + " sendSupportLocationInfo()", ex.getMessage());
            throw new NetworkException(ex.getMessage());
        }
        return packet;
    }

    public TargetInfoPacket getAthleteNextTargetLocationInfo(int athleteId)
            throws NetworkException, ServerException {
        try {
            String restfulURL = SERVER_URL + "/restfulservice/nexttargetinfo";
            List<NameValuePair> data = new ArrayList<NameValuePair>();

            HttpPost httppost = new HttpPost();
            httppost.setURI(new URI(restfulURL));
            data.add(new BasicNameValuePair("userId", String.valueOf(athleteId)));
            httppost.setEntity(new UrlEncodedFormEntity(data));

            if (httpClient == null)
                init();

            HttpResponse response = httpClient.execute(httppost);

            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 200 && statusCode != 201) {
                String errorString = response.getStatusLine().getReasonPhrase();
                Utility.error(this.getClass().toString() + " getAthleteNextTargetLocationInfo()", errorString);
                throw new ServerException(errorString, statusCode);
            } else {
                String resultString = Utility.getResultFromHttpResponse(response);
                Utility.debug(this.getClass().toString() + " getAthleteNextTargetLocationInfo()", "Athlete Next Target Location Fetched");
                return gson.fromJson(resultString, TargetInfoPacket.class);
            }
        } catch (Exception ex) {
            Utility.error(this.getClass().toString() + " getAthleteNextTargetLocationInfo()", ex.getMessage());
            throw new NetworkException(ex.getMessage());
        }
    }

    public LocationInfoPacket getNextHotspotLocation(int athleteId)
            throws NetworkException, ServerException {
        try {
            String restfulURL = SERVER_URL + "/restfulservice/nexthotspot";
            List<NameValuePair> data = new ArrayList<NameValuePair>();

            HttpPost httppost = new HttpPost();
            httppost.setURI(new URI(restfulURL));
            data.add(new BasicNameValuePair("athleteId", String.valueOf(athleteId)));
            httppost.setEntity(new UrlEncodedFormEntity(data));

            if (httpClient == null)
                init();
            HttpResponse response = httpClient.execute(httppost);

            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 200 && statusCode != 201) {
                String errorString = response.getStatusLine().getReasonPhrase();
                Utility.error(this.getClass().toString() + " getNextHotspotLocation()", errorString);
                throw new ServerException(errorString, statusCode);
            } else {
                String resultString = Utility.getResultFromHttpResponse(response);
                Utility.debug(this.getClass().toString() + " getNextHotspotLocation()", "Next Hotspot Location Fetched");
                return gson.fromJson(resultString, LocationInfoPacket.class);
            }
        } catch (Exception ex) {
            Utility.error(this.getClass().toString() + " getNextHotspotLocation()", ex.getMessage());
            throw new NetworkException(ex.getMessage());
        }
    }

    //TODO sent current location??
    public TargetInfoPacket getNextTargetLocation(int athleteId)
            throws NetworkException, ServerException {
        try {
            String restfulURL = SERVER_URL + "/restfulservice/nexttargetinfo";
            List<NameValuePair> data = new ArrayList<NameValuePair>();

            HttpPost httppost = new HttpPost();
            httppost.setURI(new URI(restfulURL));
            data.add(new BasicNameValuePair("athleteId", String.valueOf(athleteId)));
            httppost.setEntity(new UrlEncodedFormEntity(data));

            if (httpClient == null)
                init();
            HttpResponse response = httpClient.execute(httppost);

            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 200 && statusCode != 201) {
                String errorString = response.getStatusLine().getReasonPhrase();
                Utility.error(this.getClass().toString() + " getNextTargetLocation()", errorString);
                throw new ServerException(errorString, statusCode);
            } else {
                String resultString = Utility.getResultFromHttpResponse(response);
                Utility.debug(this.getClass().toString() + " getNextTargetLocation()", "Next Target Location Fetched");
                return gson.fromJson(resultString, TargetInfoPacket.class);
            }
        } catch (Exception ex) {
            Utility.error(this.getClass().toString() + " getNextTargetLocation()", ex.getMessage());
            throw new NetworkException(ex.getMessage());
        }
    }

    public TeamMemberLocationPacket getTeamMemberLocationInfoOfSupport(int athleteId)
            throws NetworkException, ServerException {
        try {
            String restfulURL = SERVER_URL + "/restfulservice/supportteammemberlocation";
            List<NameValuePair> data = new ArrayList<NameValuePair>();

            HttpPost httppost = new HttpPost();
            httppost.setURI(new URI(restfulURL));
            data.add(new BasicNameValuePair("athleteId", String.valueOf(athleteId)));
            httppost.setEntity(new UrlEncodedFormEntity(data));

            if (httpClient == null)
                init();
            HttpResponse response = httpClient.execute(httppost);

            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 200 && statusCode != 201) {
                String errorString = response.getStatusLine().getReasonPhrase();
                Utility.error(this.getClass().toString() + " getTeamMemberLocationInfoOfSupport():  ", errorString);
                throw new ServerException(errorString, statusCode);
            } else {
                String resultString = Utility.getResultFromHttpResponse(response);
                Utility.debug(this.getClass().toString() + " getTeamMemberLocationInfoOfSupport(): ", "Team Member Location Info of Support Fetched");
                return gson.fromJson(resultString, TeamMemberLocationPacket.class);
            }
        } catch (Exception ex) {
            Utility.error(this.getClass().toString() + " getTeamMemberLocationInfoOfSupport()", ex.getMessage());
            throw new NetworkException(ex.getMessage());
        }
    }

    public StartRouteStatus startRoute(int routeId) throws NetworkException, ServerException {
        StartRouteStatus routeStartStatus = StartRouteStatus.ServerError;
        try {
            String restfulURL = SERVER_URL + "/restfulservice/startroute";
            List<NameValuePair> data = new ArrayList<NameValuePair>();

            HttpPost httppost = new HttpPost();
            httppost.setURI(new URI(restfulURL));
            data.add(new BasicNameValuePair("routeId", String.valueOf(routeId)));
            httppost.setEntity(new UrlEncodedFormEntity(data));

            if (httpClient == null)
                init();
            HttpResponse response = httpClient.execute(httppost);

            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 200 && statusCode != 201) {
                String errorString = response.getStatusLine().getReasonPhrase();
                Utility.error(this.getClass().toString() + " startRoute()", errorString);
                throw new ServerException(errorString, statusCode);
            } else {
                String resultString = Utility.getResultFromHttpResponse(response);
                routeStartStatus = gson.fromJson(resultString, StartRouteStatus.class);
                if (routeStartStatus == StartRouteStatus.OK) {
                    Utility.debug(this.getClass().toString() + " startRoute()", "Route Started");
                }
            }
        } catch (Exception ex) {
            Utility.error(this.getClass().toString() + " startRoute()", ex.getMessage());
            throw new NetworkException(ex.getMessage());
        }
        return routeStartStatus;
    }

    public RouteInfoCollection getRouteDetailsForAthlete(String userName) throws NetworkException, ServerException {
        RouteInfoCollection allRouteInfoPacket = new RouteInfoCollection();
        try {
            String restfulURL = SERVER_URL + "/restfulservice/routeInfo";
            List<NameValuePair> data = new ArrayList<NameValuePair>();

            HttpPost httppost = new HttpPost();
            httppost.setURI(new URI(restfulURL));
            data.add(new BasicNameValuePair("username", String.valueOf(userName)));
            httppost.setEntity(new UrlEncodedFormEntity(data));

            if (httpClient == null)
                init();
            HttpResponse response = httpClient.execute(httppost);

            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 200 && statusCode != 201) {
                String errorString = response.getStatusLine().getReasonPhrase();
                Utility.error(this.getClass().toString() + " getRouteDetailsForAthlete()", errorString);
                throw new ServerException(errorString, statusCode);
            } else {
                String resultString = Utility.getResultFromHttpResponse(response);
                Utility.debug(this.getClass().toString() + " getRouteDetailsForAthlete()", "Athlete Route Details fetched");
                return gson.fromJson(resultString, RouteInfoCollection.class);
            }
        } catch (Exception ex) {
            Utility.error(this.getClass().toString() + " getRouteDetailsForAthlete()", ex.getMessage());
            throw new NetworkException(ex.getMessage());
        }
    }


    public UserInfo fetchUserInfo(String userName, int routeId) throws NetworkException, ServerException {
        UserInfo packet = new UserInfo();
        try {
            String restfulURL = SERVER_URL + "/restfulservice/getUserInfo";
            List<NameValuePair> data = new ArrayList<NameValuePair>();

            HttpPost httppost = new HttpPost();
            httppost.setURI(new URI(restfulURL));
            data.add(new BasicNameValuePair("username", String.valueOf(userName)));
            data.add(new BasicNameValuePair("routeId", String.valueOf(routeId)));
            httppost.setEntity(new UrlEncodedFormEntity(data));

            if (httpClient == null)
                init();
            HttpResponse response = httpClient.execute(httppost);

            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 200 && statusCode != 201) {
                String errorString = response.getStatusLine().getReasonPhrase();
                Utility.error(this.getClass().toString() + " fetchUserInfo()", errorString);
                throw new ServerException(errorString, statusCode);
            } else {
                String resultString = Utility.getResultFromHttpResponse(response);
                packet = gson.fromJson(resultString, com.hiof.beans.UserInfo.class);
                Utility.debug(this.getClass().toString() + " fetchUserInfo()", "User Info fetched");
            }
        } catch (Exception ex) {
            Utility.error(this.getClass().toString() + " fetchUserInfo()", ex.getMessage());
            throw new NetworkException(ex.getMessage());
        }
        return packet;
    }

    public GeoPoint getAthleteLatestLocationInfo(int athleteId)
            throws NetworkException, ServerException {
        GeoPoint latestAthleteLocationInfo = new GeoPoint();
        try {
            List<NameValuePair> data = new ArrayList<NameValuePair>();
            String restfulURL = SERVER_URL + "/restfulservice/athleteLatestLocation";
            HttpPost httppost = new HttpPost();
            httppost.setURI(new URI(restfulURL));

            data.add(new BasicNameValuePair("athleteId", String.valueOf(athleteId)));
            httppost.setEntity(new UrlEncodedFormEntity(data));

            if (httpClient == null)
                init();

            HttpResponse response = httpClient.execute(httppost);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 200 && statusCode != 201) {
                String errorString = response.getStatusLine().getReasonPhrase();
                Utility.error(this.getClass().toString() + " getAthleteLatestLocationInfo()", errorString);
                throw new ServerException(errorString, statusCode);
            } else {
                String resultString = Utility.getResultFromHttpResponse(response);
                latestAthleteLocationInfo = gson.fromJson(resultString, GeoPoint.class);
                Utility.debug(this.getClass().toString() + " getAthleteLatestLocationInfo()", "Athlete Location Info fetched");
            }
        } catch (Exception ex) {
            Utility.error(this.getClass().toString() + " getAthleteLatestLocationInfo()", ex.getMessage());
            throw new NetworkException(ex.getMessage());
        }
        return latestAthleteLocationInfo;
    }

    //TODO: add necessary code if needed
    public LoginStatus logIn(String userName, String password) throws NetworkException, ServerException {
        LoginStatus loginStatus = LoginStatus.UsernamePasswordNotMatch;
        try {
            String restfulURL = SERVER_URL + "/restfulservice/login";
            List<NameValuePair> data = new ArrayList<NameValuePair>();

            HttpPost httppost = new HttpPost();
            httppost.setURI(new URI(restfulURL));
            data.add(new BasicNameValuePair("username", String.valueOf(userName)));
            data.add(new BasicNameValuePair("password", String.valueOf(password)));
            httppost.setEntity(new UrlEncodedFormEntity(data));

            if (httpClient == null)
                init();
            HttpResponse response = httpClient.execute(httppost);

            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 200 && statusCode != 201) {
                String errorString = response.getStatusLine().getReasonPhrase();
                Utility.error(this.getClass().toString() + " login()", errorString);
                throw new ServerException(errorString, statusCode);
            } else {
                String resultString = Utility.getResultFromHttpResponse(response);
                loginStatus = gson.fromJson(resultString, LoginStatus.class);
                if (loginStatus == LoginStatus.OK) {
                    Utility.debug(this.getClass().toString() + " login()", "Log in Successful");
                }
            }
        } catch (Exception ex) {
            Utility.error(this.getClass().toString() + " login()", ex.getMessage());
            throw new NetworkException(ex.getMessage());
        }
        return loginStatus;
    }

    public void sendAthletePictureMedia(int userId, int athleteId, double lat, double lng, double height, MediaSendPacket packet) throws NetworkException, ServerException {
        String restfulURL = SERVER_URL + "/restfulservice/liveMedia/athlete";
        sendMedia(restfulURL, userId, athleteId, lat, lng, height, packet);
    }

    public void sendSupportPictureMedia(int userId, int athleteId, double lat, double lng, double height, MediaSendPacket packet) throws NetworkException, ServerException {
        String restfulURL = SERVER_URL + "/restfulservice/liveMedia/support";
        sendMedia(restfulURL, userId, athleteId, lat, lng, height, packet);
    }

    public void sendMedia(String url, int userId, int athleteId, double lat, double lng, double height, MediaSendPacket packet) throws NetworkException, ServerException {
        try {
            HttpPost httppost = new HttpPost();
            httppost.setURI(new URI(url));
            MultipartEntity mpEntity = new MultipartEntity(
                    HttpMultipartMode.BROWSER_COMPATIBLE);

            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            Bitmap bm = packet.getBitmap();
            bm.compress(Bitmap.CompressFormat.JPEG, 75, bos);
            byte[] byteData = bos.toByteArray();
            ByteArrayBody bab = new ByteArrayBody(byteData, packet.getFileName());
            Utility.debug(this.getClass().toString(), "sending media with userID:" + userId);
            mpEntity.addPart("userId", new StringBody(String.valueOf(userId)));
            mpEntity.addPart("athleteId", new StringBody(String.valueOf(athleteId)));
            mpEntity.addPart("file", bab);
            mpEntity.addPart("lat", new StringBody(String.valueOf(lat)));
            mpEntity.addPart("lng", new StringBody(String.valueOf(lng)));
            mpEntity.addPart("height", new StringBody(String.valueOf(height)));

            httppost.setEntity(mpEntity);

            if (httpClient == null)
                init();

            HttpResponse response = httpClient.execute(httppost);
            bm.recycle();

            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 200 && statusCode != 201) {
                String errorString = response.getStatusLine().getReasonPhrase();
                Utility.error(this.getClass().toString() + " sendMedia()", errorString);
                throw new ServerException(errorString, statusCode);
            } else {
                Utility.debug(this.getClass().toString() + " sendMedia()", "Picture sent successfully!!");
            }
        } catch (Exception ex) {
            Utility.error(this.getClass().toString() + " sendMedia()", ex.getMessage());
            throw new NetworkException(ex.getMessage());
        }
    }

    public AcknowledgePacket sendAthleteHeartRate(int athleteId, int heartRate) throws NetworkException, ServerException {
        AcknowledgePacket packet = new AcknowledgePacket("ok");
        try {
            List<NameValuePair> data = new ArrayList<NameValuePair>();
            String restfulURL = SERVER_URL + "/restfulservice/heartrate";
            HttpPost httppost = new HttpPost();
            httppost.setURI(new URI(restfulURL));
            data.add(new BasicNameValuePair("athleteId", String.valueOf(athleteId)));
            data.add(new BasicNameValuePair("heartRate", String.valueOf(heartRate)));
            httppost.setEntity(new UrlEncodedFormEntity(data));

            if (httpClient == null)
                init();
            HttpResponse response = httpClient.execute(httppost);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 200 && statusCode != 201) {
                String errorString = response.getStatusLine().getReasonPhrase();
                packet.setMessage("Server Error");
                Utility.error(this.getClass().toString() + " sendAthleteHeartRate()", errorString);
                throw new ServerException(errorString, statusCode);
            } else {
                Utility.debug(this.getClass().toString() + " sendAthleteHeartRate()", "Athlete's heart rate sent to server");
            }
        } catch (Exception ex) {
            Utility.error(this.getClass().toString() + " sendAthleteHeartRate()", ex.getMessage());
            throw new NetworkException(ex.getMessage());
        }
        return packet;
    }
}